using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Spike.ContractBuilder.Host
{
    public class TestResult
    {
        public TestResult(Type typeOfResponse, TestCondition[] conditions, string content)
        {
            _typeOfResponse = typeOfResponse;
            _conditions = conditions;
            _content = content;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="typeOfResponse"></param>
        /// <param name="conditions"></param>
        /// <param name="content"></param>
        /// <param name="extraTypes">used for serialization of unknown types</param>
        public TestResult(Type typeOfResponse, TestCondition[] conditions, string content, Type[] extraTypes)
        {
            _typeOfResponse = typeOfResponse;
            _conditions = conditions;
            _content = content;
            _extraTypes = extraTypes;
        }

        private Type _typeOfResponse;

        public Type TypeOfResponse
        {
            get { return _typeOfResponse; }
            set { _typeOfResponse = value; }
        }

        private TestCondition[] _conditions;

        public TestCondition[] Conditions
        {
            get { return _conditions; }
            set { _conditions = value; }
        }

        private string _content;

        public string Content
        {
            get { return _content; }
            set { _content = value; }
        }

        private Type[] _extraTypes = null;

        public Type[] ExtraTypes
        {
            get { return _extraTypes; }
            set { _extraTypes = value; }
        }
	

        // TODO: create the condition object

        public bool Evaluate(object[] parameters, out object response)
        {
            // with the supplied parameters, should the result be returned
            // check against the conditions
            // if it does not evaluate against the supplied parameters, then return false
            bool isTrue = true;

            foreach (TestCondition condition in _conditions)
            {
                isTrue = Test(parameters, condition);

                if (isTrue == false)
                    break;
            }

            if (isTrue)
            {
                if(_extraTypes != null)
                    response = ContractHelper.DeserializeObject(this.TypeOfResponse, this.Content, this.ExtraTypes);
                else
                    response = ContractHelper.DeserializeObject(this.TypeOfResponse, this.Content);
                return true;
            }
            else
            {
                response = null;
                return false;
            }
        }

        private bool Test(object[] parameters, TestCondition condition)
        {            
            object test = parameters[condition.Index];

            return TestChain(test, condition);
        }

        private bool TestChain(object test, TestCondition condition)
        {
            // if not chain condition the return true
            if (condition == null)
                return true;

            bool resp = false;
            try
            {
                switch (condition.Operator)
                {
                    case TestCondition.ComparisionOperator.isNotNull:
                        if (test != null)
                            resp = true && TestChain(test, condition.Chain);
                        break;
                    case TestCondition.ComparisionOperator.isNull:
                        if (test == null)
                            resp = true;
                        break;
                    case TestCondition.ComparisionOperator.equalTo:
                        resp = Comparer(test, condition.Value) == 0;
                        break;
                    case TestCondition.ComparisionOperator.equalToGreaterThan:
                        resp = Comparer(test, condition.Value) >= 0;
                        break;
                    case TestCondition.ComparisionOperator.equalToLessThan:
                        resp = Comparer(test, condition.Value) <= 0;
                        break;
                    case TestCondition.ComparisionOperator.greaterThan:
                        resp = Comparer(test, condition.Value) > 0;
                        break;
                    case TestCondition.ComparisionOperator.lessThan:
                        resp = Comparer(test, condition.Value) < 0;
                        break;
                    case TestCondition.ComparisionOperator.memberExists:
                        object member = null;
                        resp = MemberExists(test, condition.Value, condition.Index, out member) == true;

                        if (resp)
                            resp = resp && TestChain(member, condition.Chain);

                        break;
                    case TestCondition.ComparisionOperator.memberMissing:                        
                        resp = MemberExists(test, condition.Value, condition.Index, out member) != true;
                        break;
                    default:
                        resp = false;
                        break;
                }
            }
            catch (Exception ex)
            {
                resp = false;
            }

            return resp;            
        }


        private bool MemberExists(object test, string value, int index, out object member)
        {
            Type typeOfTest = test.GetType();
            PropertyInfo prop = typeOfTest.GetProperty(value);

            if (prop == null)
            {
                member = null;
                return false;
            }
            else
            {                                
                member = prop.GetValue(test, null);

                if (index >= 0)
                {
                    // test if array
                    Type memberType = member.GetType();
                    if (memberType.IsArray)
                    {
                        member = ((object[])member)[index];
                    }
                    if (memberType.IsGenericType)
                    {
                        member = memberType.GetMethod("get_Item").Invoke(member, new object[] { index });
                    }
                }
                return true;
            }
        }

        private static int Comparer(object test, string value)
        {

            Type typeOfTest = test.GetType();
            object valueObject = null;

            if (typeOfTest == typeof(string))
                valueObject = value;
            else
            {
                MethodInfo parse = typeOfTest.GetMethod("Parse", new Type[] { typeof(string) });

                if (parse == null)
                {
                    throw new ApplicationException("Only comparison of core library values are allowed.");
                }
                else
                {
                    valueObject = parse.Invoke(null, new object[] { value });
                }
            }


            return System.Collections.Comparer.Default.Compare(test, valueObject);
        }
    }
}
